require 'digest/sha1'
class User < ActiveRecord::Base
  attr_accessor :signup_was_completed
  attr_accessor :post_signup_url
  
  # Virtual attribute for the unencrypted password
  VOWELS = 'aeiou'.split ''
  CONSONANTS = 'bcdfghjklmnpqrstvwxyz'.split ''
  DIGITS = '1234567890'.split ''
  
  attr_accessor :password

  validates_presence_of     :login, :email
  validates_presence_of     :password,                   :if => :password_required?
  validates_presence_of     :password_confirmation,      :if => :password_required?
  validates_length_of       :password, :within => 4..40, :if => :password_required?
  validates_confirmation_of :password,                   :if => :password_required?
  validates_numericality_of :events_per_year
  validates_length_of       :login,    :within => 3..40
  validates_length_of       :email,    :within => 3..100
  validates_uniqueness_of   :login, :email, :case_sensitive => false
  before_save :encrypt_password
  
  after_save do |user|
    user.notify_signup_complete(user.post_signup_url) if user.signup_was_completed
  end

  has_many :availabilities, :dependent => :destroy
  has_many :attendances, :dependent => :destroy

  # Authenticates a user by their login name and unencrypted password.  Returns the user or nil.
  def self.authenticate(login, password)
    u = find_by_login(login) # need to get the salt
    u && u.authenticated?(password) ? u : nil
  end
  
  def self.no_managers_exist?
    find(:all, :conditions => ['manager=true']).empty?
  end
    
  # Encrypts some data with the salt.
  def self.encrypt(password, salt)
    Digest::SHA1.hexdigest("--#{salt}--#{password}--")
  end
  
  def self.find_all
    find(:all, :order => 'login asc')
  end

  # Encrypts the password with the user salt
  def encrypt(password)
    self.class.encrypt(password, salt)
  end

  def authenticated?(password)
    signup_complete? && crypted_password == encrypt(password)
  end

  def remember_token?
    remember_token_expires_at && Time.now.utc < remember_token_expires_at 
  end

  # These create and unset the fields required for remembering users between browser closes
  def remember_me
    self.remember_token_expires_at = 2.weeks.from_now.utc
    self.remember_token            = encrypt("#{email}--#{remember_token_expires_at}")
    save(false)
  end

  def forget_me
    self.remember_token_expires_at = nil
    self.remember_token            = nil
    save(false)
  end
  
  def is_available_for(event)
    a = Availability.new(:event => event)
  end

  def attend(event, level)
    a = availability(event)
    if a.nil?
      availabilities << Availability.new(:event => event, :user => self, :level => level)
    else
      a.update_attributes(:level => level)
    end
  end
  
  def unattend(event)
    a = availability(event)
    a.destroy unless a.nil?
  end

  def assign_to(event)
    if attendance(event).nil?
      a = Attendance.new(:event => event, :user => self)
      a.save
      attendances << a
    else
      logger.debug(attendance(event).to_yaml)
    end
  end
  
  def deassign_from(event)
    attendance = attendance(event)
    attendance.destroy unless attendance.nil?
  end

  def can_attend?(event)
    a = availability(event)
    return false if a.nil?
    a.level
  end
  
  def is_assigned_to?(event)
    ! attendance(event).nil?
  end
  
  def render
    username = "#{first_name} #{last_name}"
    username == ' ' ? login : username
  end
  
  def salutation
    first_name || login
  end
  
  def sections(user)
    if user != :false and user.manager?
      SECTIONS
    else
      SECTIONS.reject{|i|[:admin].member? i.label}
    end
  end
  
  def filter_attributes(params)
    unless manager?
      SECTIONS.find{|s|s.label==:admin}.fields.map{|f|f.name}.each do |n|
        params.delete n
      end
    end
    params
  end
  
  def send_new_password!(url)
    new_password = ''
    new_password << CONSONANTS[(rand * 21).to_i]
    new_password << VOWELS[(rand * 5).to_i]
    new_password << VOWELS[(rand * 5).to_i]
    new_password << CONSONANTS[(rand * 21).to_i]
    new_password << DIGITS[(rand * 10).to_i]
    new_password << DIGITS[(rand * 10).to_i]
    new_password << CONSONANTS[(rand * 21).to_i]
    new_password << VOWELS[(rand * 5).to_i]
    new_password << VOWELS[(rand * 5).to_i]
    new_password << CONSONANTS[(rand * 21).to_i]
    self.password = new_password
    self.password_confirmation = new_password
    self.save
    EventMailer.deliver_notification(self, :password_reset.l, :password_reset_message.localize_with_args(:login => login, :password => password, :url => url))
  end
  
  def notify_signup_complete(url)
    EventMailer.deliver_notification(self, :signup_complete.l, :signup_complete_message.localize_with_args(attributes.merge(:url => url)))
  end
  
  protected
    # before filter 
    def encrypt_password
      return if password.blank?
      self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
      self.crypted_password = encrypt(password)
    end
    
    def password_required?
      crypted_password.blank? || !password.blank?
    end
    
    def attendance(event)
      a = self.attendances.find_all{|i|i.event_id==event.id}
      return nil if a.size == 0
      return a.first
    end
    
    def availability(event)
      a = availabilities.find_all{|i|i.event_id==event.id}
      return nil if a.size == 0
      return a.first
    end

    class Section
      attr_reader :fields
      attr_reader :label

      def initialize(label)
        @label = label
        @fields = []
      end

      def <<(field)
        @fields << field
      end

      def l
        :"profile_section_#{label}".l
      end

      def ==(other)
        label == other.label
      end
    end

    class Field
      attr_reader :name

      def initialize(name, type, options)
        @name = name
        @field_type = type
        @options = options
      end

      def options(signup=false)
        signup ? @options.reject{|k,v|k==:disabled} : @options
      end

      def type
        @field_type
      end

      def render(form, signup=false)
        form.send(@field_type, @name, options(signup))
      end

      def value(object)
        object.send @name
      end

      def l
        :"profile_field_#{@name}".l
      end
    end
    
    class BooleanField < Field
      def initialize(name, options)
        super(name, :check_box, options)
      end
      
      def value(object)
        object.send(:"#{@name}?") ? :yes.l : :no.l
      end
    end

    class SelectField < Field
      def initialize(name, choices, options)
        super(name, :select, options)
        @choices = choices
      end

      def render(form, signup=false)
        form.select(@name, @choices, options(signup))
      end
    end

    SECTIONS = []

    def self.field(name, type, section, options={})
      find_section(section) << Field.new(name, type, options)
    end

    def self.select_field(name, choices, section, options={})
      find_section(section) << SelectField.new(name, choices, options)
    end
    
    def self.boolean_field(name, section, options={})
      find_section(section) << BooleanField.new(name, options)
    end

    def self.find_section(section)
      s = SECTIONS.find{|i|i==Section.new(section)}
      if s.nil?
        s = Section.new(section)
        SECTIONS << s
      end
      s
    end

    field :login, :text_field, :personal_details, :disabled => true
    field :first_name, :text_field, :personal_details
    field :last_name, :text_field, :personal_details
    field :email, :text_field, :personal_details
    field :mobile_phone, :text_field, :personal_details
    field :home_phone, :text_field, :personal_details
    field :address, :text_area, :personal_details
    field :post_town, :text_field, :personal_details
    field :post_code, :text_field, :personal_details
    field :dietary_needs, :text_field, :diet
    boolean_field :share_contact_details, :personal_details
    field :events_per_year, :text_field, :personal_details

    # The following should only be editable by a manager
    field :branch_joining_date, :date_select, :admin
    select_field :current_status, ["Volunteer", "PV", "Leave of absence"], :admin
    select_field :current_role, ["Volunteer", "Committee", "DG", "Director"], :admin
    boolean_field :shift_leader, :admin
    boolean_field :festival_leader, :admin
    boolean_field :manager, :admin

    field :allergies, :text_field, :diet

    boolean_field :can_drive, :transport
    boolean_field :has_own_car, :transport

end
